package com.google.code.expr.runtime;

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
import java.util.Stack;

import com.google.code.expr.lang.EXPRAtom;
import com.google.code.expr.lang.EXPRAtomReader;
import com.google.code.expr.lang.EXPRBlockComponent;
import com.google.code.expr.lang.EXPRConditionalCase;
import com.google.code.expr.lang.EXPRConditonalType;
import com.google.code.expr.lang.EXPREcho;
import com.google.code.expr.lang.EXPRParseException;
import com.google.code.expr.lang.EXPRSet;
import com.google.code.expr.lang.EXPRStream;

public class EXPRStreamParser {
	
	private EXPRRunTimeState mState = new EXPRRunTimeState();
	private Stack<EXPRStackState> mStack = new Stack<EXPRStackState>();
	private EXPRAtomReader mReader;
	
	public EXPRStreamParser(EXPRAtomReader pReader) {
		mReader = pReader;
		mStack.push(new EXPRStackRootState());
	}

	public void setInitialState(Properties pProperties) {
		for (Object key : pProperties.keySet()) {
			String varName = key.toString();
			mState.setVariableFromString(varName, pProperties.getProperty(varName));
		}
	}
	
	/**
	 * closes the atom reader.
	 * 
	 * @throws IOException
	 */
	public void close() throws IOException {
		mReader.close();
	}
	

	public String readNextStream() throws IOException, EXPRParseException, EXPRRunTimeException { 
		EXPRAtom nextAtom = mReader.read();
		while (nextAtom!=null) {
			
			if (nextAtom instanceof EXPRConditionalCase) {
				EXPRConditionalCase cond = (EXPRConditionalCase)nextAtom;
				if (cond.getType().equals(EXPRConditonalType.IF)) {
					EXPRStackConditionalState stackState = new EXPRStackConditionalState(mStack.peek().isOutputActive());
					mStack.push(stackState);
					EXPRValue result = cond.getExpression().resolveToValue(mState);
					if (result.isBoolean() && result.getBoolean().booleanValue()) {
						stackState.setFoundTrueCondition(true);
						stackState.setOutputActive(true);
					}
					else {
						stackState.setOutputActive(false);
					}
				}
				else if (cond.getType().equals(EXPRConditonalType.ELSEIF)) {
					EXPRStackState stackState = mStack.peek();
					if (!(stackState instanceof EXPRStackConditionalState)) {
						throw new EXPRParseException(cond, "Unexpected conditional "+cond.getType()+" found.");
					}
					EXPRStackConditionalState condStackState = (EXPRStackConditionalState)stackState;
					if (condStackState.hasEncounteredElse()) {
						throw new EXPRParseException(cond, "Encountered an "+EXPRConditonalType.ELSEIF+" after an "+EXPRConditonalType.ELSE);
					}
					EXPRValue result = cond.getExpression().resolveToValue(mState);
					if (!condStackState.isOutputActive() && result.isBoolean() && result.getBoolean().booleanValue()) {
						condStackState.setFoundTrueCondition(true);
						condStackState.setOutputActive(true);
					}
					else {
						condStackState.setOutputActive(false);
					}
				}
				else if (cond.getType().equals(EXPRConditonalType.ELSE)) {
					EXPRStackState stackState = mStack.peek();
					if (!(stackState instanceof EXPRStackConditionalState)) {
						throw new EXPRParseException(cond, "Unexpected conditional "+cond.getType()+" found.");
					}
					EXPRStackConditionalState condStackState = (EXPRStackConditionalState)stackState;
					if (condStackState.hasEncounteredElse()) {
						throw new EXPRParseException(cond, "Encountered an "+EXPRConditonalType.ELSE+" after an "+EXPRConditonalType.ELSE);
					}
					if (!condStackState.isOutputActive()) {
						condStackState.setFoundTrueCondition(true);
						condStackState.setOutputActive(true);
					}
					else {
						condStackState.setOutputActive(false);
					}
				}
				else if (cond.getType().equals(EXPRConditonalType.ENDIF)) {
					EXPRStackState stackState = mStack.pop();
					if (!(stackState instanceof EXPRStackConditionalState)) {
						throw new EXPRParseException(cond, "Unexpected conditional "+cond.getType()+" found.");
					}
					
				}
				else {
					throw new EXPRParseException(cond, "Unexpected conditional "+cond.getType()+" found.");
				}
			}
			else if (nextAtom instanceof EXPRBlockComponent) {
				EXPRStackState stackState = mStack.peek();
				if (stackState.isOutputActive()) {
					EXPRBlockComponent pComponent = (EXPRBlockComponent)nextAtom;
					if (pComponent instanceof EXPRStream) {
						String result = executeStream((EXPRStream)pComponent);
						if (result!=null) {
							return result;
						}
					}
					else if (pComponent instanceof EXPREcho) {
						String result = executeEcho((EXPREcho)pComponent);
						if (result!=null) {
							return result;
						}
					}
					else if (pComponent instanceof EXPRSet) {
						executeSet((EXPRSet)pComponent);
					}
				}
			}
			else {
				throw new EXPRParseException(nextAtom, "Encountered unexpected type "+nextAtom.getClass().getName());
			}
			nextAtom = mReader.read();
		}
		
		//we are done reading the source
		return null;
	}
	
	protected String executeStream(EXPRStream pStream) throws IOException,EXPRRunTimeException {
		return pStream.getContent();
	}

	protected String executeEcho(EXPREcho pEcho) throws IOException,EXPRRunTimeException {
		return pEcho.getOperand().resolveToString(mState);
	}
	
	protected void executeSet(EXPRSet pSet) throws IOException,EXPRRunTimeException {
		EXPRVariable var = mState.getVariable(pSet.getVariable().getName());
		if (var==null) {
			var = new EXPRVariable(pSet.getVariable().getName());
			mState.setVariable(var.getName(), var);
		}
		var.setValue(pSet.getOperand().resolveToValue(mState));
	}
	
	
	interface EXPRStackState {
		public boolean isOutputActive();
	}
	class EXPRStackRootState implements EXPRStackState{
		public boolean isOutputActive() {
			return true;
		}
	}
	class EXPRStackConditionalState implements EXPRStackState{
		private boolean mEncounteredElse;
		private boolean mFoundTrueCondition;
		private boolean mParentActive;
		private boolean mActive;
		
		public EXPRStackConditionalState(boolean pParentActive) {
			mEncounteredElse = false;
			mFoundTrueCondition = false;
			mActive = false;
			mParentActive = pParentActive;
		}

		public boolean hasEncounteredElse() {
			return mEncounteredElse;
		}

		public void setHasEncounteredElse(boolean mEncounteredElse) {
			this.mEncounteredElse = mEncounteredElse;
		}

		public boolean isOutputActive() {
			return mParentActive && mActive;
		}

		public void setOutputActive(boolean pActive) {
			mActive = pActive;
		}
		
		public void setFoundTrueCondition(boolean pFoundTrueCondition) {
			this.mFoundTrueCondition = pFoundTrueCondition;
		}
		
		public boolean hasFoundTrueCondition() {
			return mFoundTrueCondition;
		}
		
	}
	public static void main(String[] pArgs) throws Exception {
		FileReader fis = new FileReader("test/simple.expr");
		EXPRAtomReader reader = new EXPRAtomReader(fis);
		reader.setTrimStream(false);

		EXPRStreamParser streamReader = new EXPRStreamParser(reader);
		Properties state = new Properties();
		state.load(new FileInputStream("test/simple.properties"));
		streamReader.setInitialState(state);
		String block = streamReader.readNextStream();
		while (block!=null) {
			System.out.println(block);
			block = streamReader.readNextStream();
		}
	}
}
